package generator.util;

import generator.datastructure.MethodDeclaration;
import harsh.javatoxml.Java2XML;
import harsh.javatoxml.Exceptions.JXMLException;
import harsh.javatoxml.data.IXMLElement;
import harsh.javatoxml.dom.Domifier;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.OutputStream;
import java.util.ArrayList;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**Esta clase sirve como herramienta para manipular documentos cuya estructura responde al formato
 * impuesto por la biblioteca Java2xml. Los métodos que implementa permiten realizar modificaciones 
 * o consultas tales como cambiar el nombre de una clase, incorporacion de sentencias import,
 * cambio del nombre de paquete al que pertence una clase, seteo de tipos de variables o tipos de retorno
 * de métodos, obtencion de la sentencia de declaración de una variable, etc.
 * @author Hirsch-Fernández*/

public class J2XMLManipulator {
	
	/**Constante numérica que indica el valor equal de una comparación*/
	protected final int EQUAL =				0;
	
	public static final int FIRST = 		0;
	
	
	/**A continuación una versión reducida de la lista de tags que emplea la biblioteca java2xml para 
	 * representar una clase java como archivo xml*/	
	
	
	/**nombre del elemento xml que indica la presencia de un bloque*/
	public final String BODYMETHOD = 		"block";
	
	/**nombre del elemento xml que indica la presencia de un envío de mensaje a un objeto (llamada a un método)*/
	public final String METHODCALL = 		"send";
	
	/**nombre del elemento xml que indica la presencia de un atributo de una clase u objeto*/
	public final String ATTRIBUTE = 		"field";
	
	/**nombre del elemento xml que sirve para indicar el atributo tipo de una variable, parámetro, función, etc*/
	public final String TYPE = 				"type";
	
	/**nombre del elemento xml que sirve para indicar la presencia de un bloque try*/
	public final String TRY = 				"try";
	
	/**nombre del elemento xml que sirve para indicar la presencia de un bloque try*/
	public final String CATCH = 				"catch";
	
	/**nombre del elemento xml que indica el como se llama el archivo xml usualmente recibe el mismo nombre que
	 * la clase a la cual describe.*/
	public final String CLASSFILENAME = 	"java-class-file";
	
	/**nombre del elemento xml que indica el mombre del paquete al que pertenece la clase que representa el
	 * archivo xml.*/
	public final String PACKAGE = 			"package-decl";
	
	/**nombre del elemento xml que indica el nombre de la clase representada por el archivo xml*/
	public final String CLASS = 			"class";
	
	/**nombre del elemento xml que indica la presencia de un parámetro formal de un método*/
	public final String PARAM = 			"formal-argument";
	
	/**nombre del elemento xml que siempre precede a todo argumento formal o conjunto de argumentos formales
	 * de un método, es decir, el nodo padre de {@link PARAM} siempre será un nodo ALLPARAMS*/
	public final String ALLPARAMS = 		"formal-arguments";
	
	/**nombre del elemento xml que indica la presencia de una declaración de método*/
	public final String METHOD = 			"method";
	
	/**nombre del elemento xml que indica la presencia de una declaración del método construtor de una clase java*/
	public final String CONSTRUCTOR = 		"constructor";
	
	/**nombre del elemento xml que indica la presencia de una sentencia de asignación*/
	public final String ASIGMENT = 			"assignment-expr";
	
	/**nombre del elemento xml que indica la existencia de una referencia a una variable de un método*/
	public final String VARREF = 			"var-ref";
	
	/**nombre del elemento xml que indica la existencia de una sentencia de creación de un objeto*/
	public final String NEW = 				"new";
	
	/**nombre del elemento xml que engloba a los parámetros reales de una llamada a un método*/
	public final String ARGUMENTS = 		"arguments";
	
	/**nombre del elemento xml que indica la presencia de una operación de casting*/
	public final String CASTEXPR = 			"cast-expr";
	
	/**nombre del elemento xml que indica la existencia de una sentencia import al inicio de la clase java*/
	public final String IMPORT = 			"import";
	
	/**nombre del elemento xml que sirve para indicar el objeto al cual se le pretende enviar un mensaje*/
	public final String TARGET = 			"target";
	
	/**nombre del elemento xml que indica la existencia de un acceso a un atributo de un objeto*/
	public final String FIELDACCESS = 		"field-access";
	
	/**nombre del elemento xml que indica la presencia de una declaración de variable dentro del cuerpo de un
	 * método*/
	public final String LOCALVARIABLE = 	"local-variable";
	
	/**nombre del elemento xml que sirve para indicar el lado izquierdo de una sentencia de asignación*/
	public final String ASSIGLEFTVALUE = 	"lvalue";
	
	/**nombre del elemento xml que indica la presencia de una constante numérica*/
	public final String NUMBER = 			"literal-number";
	
	/**nombre del elemento xml que indica la presencia de una cadena alfanumérica*/
	public final String STRING = 			"literal-string";
	
	/**nombre del elemento xml que indica la existencia de una sentencia condicional*/
	public final String BRANCH = 			"if";	

	/**nombre del elemento xml que indica la presencia de una autoreferencia*/
	public final String THIS = 				"this";
	
	/**nombre del elemento xml que indica la existencia de una sentencia return*/
	public final String RETURN = 			"return";
	
	/**nombre del elemento xml que indica el comienzo de la declaración de una interface java*/
	public final String INTERFACE = 		"interface";
				
	/**nombre del elemento xml que indica la presencia de una sentencia throws en la declaración de un método*/
	public final String THROWS = 			"throws";
	
	/**nombre del elemento que indica la presencia de un tipo parametrizado. Como hijo de este elemento se encuentra
	 * un elemento {@link TYPE}} que indica el tipo de la parametrización*/
	public final String PARAMETRIZED_TYPE = "type-argument";
	
	/**nombre del elemento xml que indica la presencia de la palabra reservada extends*/
	public final String EXTENDS = 			"extends-list";
	
	/**nombre del elemento xml que indica la presencia de la palabra reservada implements*/
	public final String IMPLEMENT = 			"implement";
	
	/**ArrayList que contiene los nombres de los tipos primitivos de java*/		
	private ArrayList<String> primitivesTypes;
	
	/**Objeto utilizado para evaluar expresiones xpath sobre archivos xml*/
	protected XPath xpath;
	
	/**Ruta donde se halla el archivo de estructuras preConstruidas utilizado para
	 * facilitar la tarea llevada a cabo en algunos métodos que ofrece la clase*/
	private String preBuildStructuresPath = "config"+System.getProperty("file.separator")+"JavaToXml"+System.getProperty("file.separator")+"preBuildStructures.xml";
	
	/**Es un documento con estructuras pre-armadas, a modo de templates. Las mismas son utilizadas a la 
	 * hora introducir modificaciones a un documento. Por ejemplo el agregado de una nueva sentencia
	 * import a una clase java provoca que se obtenga el nodo genérico que representa un elemento import para
	 * luego completarlo con los datos correspondientes*/
	protected Document preBuildStructures;
	
	/**Constructor de la clase que retorna una instancia de DocumentModifier a partir de la cual se pueden
	 * efectuar modificaciones sobre documentos que respeten la estructura impuesta por la biblioteca java2xml*/
	public J2XMLManipulator(){
		
		xpath = XPathFactory.newInstance().newXPath();
		preBuildStructures = DocTransformer.xmlToDom(new File(preBuildStructuresPath)); 
		
		primitivesTypes = new ArrayList<String>();
		primitivesTypes.add("int");
		primitivesTypes.add("long");
		primitivesTypes.add("short");
		primitivesTypes.add("boolean");
		primitivesTypes.add("char");
		primitivesTypes.add("double");
		primitivesTypes.add("float");
		primitivesTypes.add("byte");		
	}
	
	/**@return retorna la ruta donde se encuentra el archivo preBuildStructures que contiene estructuras
	 * genéricas pre armadas utilizadas por esta clase para llevar a cabo sus operaciones*/
	public String getPreBuildStructuresPath() {
		return preBuildStructuresPath;
	}


	/**@param ruta donde se encuentra el archivo preBuildStructures que contiene estructuras
	 * genéricas pre-armadas utilizadas por esta clase para llevar a cabo sus operaciones*/
	public void setPreBuildStructuresPath(String preBuildStructuresPath) {
		this.preBuildStructuresPath = preBuildStructuresPath;
		preBuildStructures = DocTransformer.xmlToDom(new File(preBuildStructuresPath));
	}

	/**Incorpora todos los nodos que representan sentencias import del documento source al documento
	 * dest. 
	 * @param source
	 * 			documento de donde se copiarán los nodos que corresponden a sentencias import
	 * @param dest
	 * 			documento  que recibirá	los nodos que representan sentencias import
	 * <BR>NOTA: ambos parámetros representan una clase java según el formato impuesto por java2xml.
	 * Se asegura que, al pretender agregar una sentencia import ya contenida en la clase representada
	 * por dest, la operación no tenga efecto y así evitar la duplicación de imports. 
	 *  */
	public void cloneImports(Document source, Document dest){
		
		try {
			NodeList allImports = (NodeList)xpath.evaluate("//"+IMPORT, source, XPathConstants.NODESET);
			if (allImports != null)
				for (int i = 0; i < allImports.getLength(); i++)					
					addImport(allImports.item(i), dest);				
					
		} catch (XPathExpressionException e) {
				System.out.println("[ERR] error en la clonación de imports");
				e.printStackTrace();
		}
	}
	
	
	/**@return true si el contenido del nodo import ya se encuentra presente en otro nodo import del
	 * 			 documento que corresponde al segundo parámetro. False en caso contrario.*/
	public boolean checkImport(Element nodeImport, Document document) throws XPathExpressionException{
		return (Boolean)xpath.evaluate("//"+IMPORT+"[@module=\""+ nodeImport.getAttribute("module")+"\"]", document, XPathConstants.BOOLEAN);
	}
	
	
	/**Este método inserta una sentencia import sobre el documento pasado como parámetro	 
	 * @param importValue
	 * 				es el nombre del paquete que formará parte de la sentencia import
	 * @param inputDoc
	 * 				documento que representa la clase sobre la que se pretende insertar la
	 * 				sentencia import.
	 * @return org.w3c.dom.Document
	 * 				documento con la sentencia import insertada. 
	 * NOTA: el documento de entrada será modificado como consecuencia de la invocación al método*/
	public void addImport(String importValue, Document inputDoc){
				
		try {
			 Element genericImportNode = (Element)xpath.evaluate("//structure[@id=\"import\"]/"+IMPORT, preBuildStructures, XPathConstants.NODE);
			 genericImportNode.setAttribute("module", importValue);
			 addImport(genericImportNode, inputDoc);			 
		} catch (XPathExpressionException e) {
			System.out.println("[ERR] No se encontro el nodo genérico import");		
		}		
	}	
	
	/**Inserta un nodo correspondiente a una sentencia import dentro de un documento que representa una clase java
	 * sobre la cual se añadirá el import.
	 * NOTA: el nodo es clonado e incorporado al documento y se asegura que no existan imports repetidos dentro de la clase. 
	 * @param foreignImportNode
	 * 				nodo import que será insertado dentro del documento
	 * @param inputDoc
	 * 				documento que representa la clase java que recibirá la inseción del import*/
	public void addImport(Node foreignImportNode, Document inputDoc){
				 
		try {
			if(!this.checkImport((Element)foreignImportNode, inputDoc)){
				Node importNode = inputDoc.importNode(foreignImportNode, true);
				/**se obtiene el nodo donde comienza la declaración de la clase para que la inserción del nodo import
				 * quede por encima, respetando la sintaxis de java*/
				Node classDeclaration = (Node)xpath.evaluate("//"+CLASS, inputDoc, XPathConstants.NODE);
				Node insertionPoint = classDeclaration.getParentNode();
				
				insertionPoint.insertBefore(importNode, classDeclaration);		
			}
		} catch (XPathExpressionException e) {
			System.out.println("[ERR] Falló la inserción del nodo import");
			e.printStackTrace();
		}					
	}
	
	/**Mediante este método se cambia el nombre a una clase java. El método también contempla el 
	 * cambio de nombre de los constructores y del archivo propiamente dicho. También son actualizadas todas 
	 * las referencias al viejo nombre de la clase pero sólo dentro de la clase.
	 * @param nameValue
	 * 				nuevo nombre de la clase
	 * @param document
	 * 				documento que representa la clase a la cual se le pretende cambiar el nombre*/
	public void setClassName(String nameValue, Document document) throws XPathExpressionException{
		
		//se actualizan las referencias al nombre de la clase con el nuevo nombre
		String className = ((String)xpath.evaluate("//"+CLASS+"[1]/@name", document, XPathConstants.STRING));		
		NodeList classRefs = ((NodeList)xpath.evaluate("//"+TYPE+"[@name='"+className+"']", document, XPathConstants.NODESET));
		for (int i = 0; i < classRefs.getLength(); i++)
			((Element)classRefs.item(i)).setAttribute("name", nameValue);
				
		//se obtiene el elemento "java-class-file" y se le setea el atributo name al valor recibido en el primer parámetro del método
		((Element)xpath.evaluate("/"+CLASSFILENAME, document, XPathConstants.NODE)).setAttribute("name", nameValue+".java");
		//se obtiene el elemento "class"(donde comienza la declaración de la clase) y se le setea el atributo name al valor recibido en el primer parámetro del método
		((Element)xpath.evaluate("//"+CLASS+"[1]", document, XPathConstants.NODE)).setAttribute("name", nameValue);
		//se cambian los nombres del/los constructores de la clase.
		NodeList constructors = document.getElementsByTagName(CONSTRUCTOR);
		for (int i = 0; i < constructors.getLength(); i++)
			((Element)constructors.item(i)).setAttribute("name", nameValue);	
				
	}
	
	/**Permite setear al documento destino el nombre del paquete indicado en el primer parámetro. 
	 * @param packageValue
	 * 			el nombre del paquete que será seteado.
	 * @param dest
	 * 			documento que será modificado con el nombre del paquete que figura en el primer parámetro
	 * </BR>NOTA: el documento representa una clases java y su estructura debe corresponderse con la impuesta
	 *  por la biblioteca Java2XML.*/
	public void setPackage(String packageValue, Document dest){		
		
		Element packageElement;
		try {
			packageElement = (Element)xpath.evaluate("/"+CLASSFILENAME+"/"+PACKAGE,dest,XPathConstants.NODE);
			if (packageValue.compareTo("") != 0){				
				if (packageElement == null){
					//chequear si funciona
					packageElement = (Element)xpath.evaluate("//[id=\"package\"]", preBuildStructures, XPathConstants.NODE);
					packageElement.setAttribute("name", packageValue);
					
					Node node = (Node)xpath.evaluate("//"+IMPORT+"[1]", dest, XPathConstants.NODE);
					if (node == null)
						node = (Node)xpath.evaluate("//"+CLASS, dest, XPathConstants.NODE);
					dest.insertBefore(packageElement, node);				
				}
				else
					packageElement.setAttribute("name", packageValue);
			}
			else
				packageElement.getParentNode().removeChild(packageElement);				
		} catch (XPathExpressionException e) {
			System.out.println("[ERR] Falló el seteo del paquete");
			e.printStackTrace();
		}		
	}
	
		
	/**Permite setear en el documento destino el nombre del paquete en el que está contenido el documento origen. 
	 * @param source
	 * 			documento del cual se extraerá el nombre del paquete al que pertenece la clase que representa.
	 * @param dest
	 * 			documento que será modificado con el nombre del paquete que figura en el documento source
	 * </BR>NOTA: ambos documentos representan clases java y sus estructuras responden a la impuesta por la
	 * biblioteca Java2XML.*/	
	public void setPackage(Document source, Document dest) throws XPathExpressionException{
					
		String packageValue = (String)xpath.evaluate("/"+CLASSFILENAME+"/"+PACKAGE+"/@name",source,XPathConstants.STRING);
		this.setPackage(packageValue, dest);		
						
	}
	
	
	
	/**Permite setear el atributo "name" de un elemento type. Dicho elemento es utilizado para representar el tipo
	 * de una variable, constante, parámetro o retorno de una función.
	 * Como consecuencia de la invocación al método se modifica el documento del segundo parámetro.
	 * @param typeValue
	 * 				valor que tomará el nombre de un elemento type
	 * @param dest
	 * 				documento donde se halla el elemento type que recibirá la modificación
	 * @param destLocation
	 * 				expresión XPath que indica el elemento type a modificar contenido dentro del documento dest*/
	public void setType(String typeValue, Document dest, String destLocation) throws XPathExpressionException{
		this.setType(typeValue, dest.getDocumentElement(), destLocation);
	}
	
	
	/**Permite setear el atributo "name" de un elemento type. Dicho elemento es utilizado para representar el tipo
	 * de una variable, constante, parámetro o retorno de una función.
	 * Como consecuencia de la invocación al método se modifica el elemento dest que corresponde al segundo parámetro.
	 * @param typeValue
	 * 				valor que tomará el nombre de un elemento type
	 * @param dest
	 * 				elemento que recibirá la modificación.
	 * @param destLocation
	 * 				expresión XPath que indica el elemento type a modificar contenido dentro del elemento dest*/
	public void setType(String typeValue, Element dest, String destLocation) throws XPathExpressionException{
				
		if (typeValue.compareTo("") != EQUAL){
			Element typeElement = (Element)xpath.evaluate(destLocation,dest,XPathConstants.NODE);
			if (typeElement != null){
				typeElement.setAttribute("name", typeValue);			
				if (primitivesTypes.contains(typeValue))
					typeElement.setAttribute("primitive", "true");				
			}			
		}				
	}
	
	/**Reemplaza el elemento type del documento dest en la ubicación descripta por destLocation. 
	 * destLocation debe ser una expresion xpath cuyo resultado de la evaluación sobre
	 * dest debe arrojar un elemento type. Los elementos type son utilizados para representar el tipo
	 * de una variable, constante, parámetro o retorno de una función.<BR>
	 * NOTA: la implentación del método tiene en cuenta la importación del nodo typeNode
	 * al documento al que pertenece el nodo dest
	 * @param typeNode
	 * 			elemento type que será importado y colocado en el documento dest
	 * @param dest
	 * 			documento que recibirá el nodo type en la ubicación destLocation
	 * @param destLocation
	 * 			expresión xpath que representa la ubicación donde se insertará el nodo typeNode dentro del documento dest*/
	public void setType(Element typeNode, Document dest, String destLocation) throws XPathExpressionException{
		
		if (typeNode != null){
			Element originalTypeNode = (Element)xpath.evaluate(destLocation,dest,XPathConstants.NODE);
			if (originalTypeNode != null){
				Node nodeImported = originalTypeNode.getOwnerDocument().importNode(typeNode, true);
				originalTypeNode.getParentNode().replaceChild(nodeImported, originalTypeNode);
			}			
		}				
	}
	
	/**Reemplaza el elemento type del elemento dest que se encuentra en la ubicación descripta por destLocation. 
	 * destLocation debe ser una expresion xpath cuyo resultado de la evaluación sobre
	 * dest debe arrojar un elemento type. Los elementos type son utilizados para representar el tipo
	 * de una variable, constante, parámetro o retorno de una función.<BR>
	 * NOTA: la implentación del método tiene en cuenta la importación del nodo typeNode
	 * al documento al que pertenece el nodo dest
	 * @param typeNode
	 * 			elemento type que será importado y colocado en el elemento dest
	 * @param dest
	 * 			elemento que recibirá el nodo type en la ubicación destLocation
	 * @param destLocation
	 * 			expresión xpath que representa la ubicación donde se insertará el nodo typeNode dentro del element dest*/
	public void setType(Element typeNode, Element dest, String destLocation) throws XPathExpressionException{
		
		if (typeNode != null){
			Element originalTypeNode = (Element)xpath.evaluate(destLocation,dest,XPathConstants.NODE);
			if (originalTypeNode != null){
				Node nodeImported = originalTypeNode.getOwnerDocument().importNode(typeNode, true);
				originalTypeNode.getParentNode().replaceChild(nodeImported, originalTypeNode);
			}			
		}				
	}
	
	/**Copia todos los atributos del parámetro sourceElement en el parámetro destElement. Si un atributo se encuentra presente
	 * en ambos elementos, el valor en destElement tomará el correspondiente de sourceElement.  
	 * Como consecuencia de la invocación al método se modifica el parámetro destElement.
	 * @param sourceElement
	 * 				elemento del que se extraerán los atributos para ser copiados en el elemento destElement
	 * @param destElement
	 * 				elemento que recibirá los atributos y valores del sourceElement 
	 * @param clear
	 * 				si es true, los atributos contenidos en destElement que no se encuentran en sourceElement serán eliminados,
	 * 				por el contrario si esta en false los mismos se preservarán.  
	 */
	public void copyAttributes(Element sourceElement, Element destElement, boolean clear){			
			 
			if (clear){
				NamedNodeMap destAttributes = destElement.getAttributes();			
				for (int i = 0; i < destAttributes.getLength(); i++)
					destElement.removeAttributeNode((Attr)destAttributes.item(i));
			}
			//seteo cada atributo-valor de typeElement en el elemento type dest 
			NamedNodeMap attributes = sourceElement.getAttributes();
			for (int j = 0; j < attributes.getLength(); j++){
				Node nodeToAdd = destElement.getOwnerDocument().importNode(attributes.item(j), true);
				destElement.setAttributeNode((Attr)nodeToAdd);
			}		
	}
	
	/**
	 * Método encargado de crear un {@link Element} con el nombre {@link tagName} y setea el atributo
	 * name del mismo con el valor {@link name}. El nuevo Element es asociado a {@link doc}.
	 * 
	 * @param doc documento en el cual se creará el nuevo {@link Element}.
	 * @param tagName nombre del nuevo {@link Element}.
	 * @param name valor para el atributo name.
	 * @return El nuevo {@link Element} creado.
	 */
	public Element createNewTag(Document doc,String tagName ,String name) {
		Element retNode = doc.createElement(tagName);
		if (!name.equals("")||!name.equals(null))
			retNode.setAttribute("name", name);
		return retNode;
	}
	
	/**
	 * El método genera un Element que representa una asignación con el valor a izquierda {@link leftValue} que pertenece
	 * a una variable de instancia (accede mediante this) y a derecha {@link rightValue}, y la asocia {@link doc}.
	 * Ejemplo:
	 * 			this.leftValue = rightValue;
	 * 
	 * @param doc Document al que se asocia el nuevo Element que representa una asignación.
	 * @param leftValue Valor del lado izquierdo de la asignación.
	 * @param rightValue Valor del lado derecho de la asignación.
	 * @throws XPathExpressionException
	 */
	protected Element setAssingmentValues(Document doc,String leftValue, String rightValue) throws XPathExpressionException {
		Element assignment = (Element) xpath.evaluate("structures/structure[@id=\"constructorAssignment\"]/"+ASIGMENT, this.preBuildStructures,XPathConstants.NODE);
		Element lvalue = (Element)xpath.evaluate(ASSIGLEFTVALUE+"/"+FIELDACCESS, assignment, XPathConstants.NODE);
		Element rvalue = (Element)xpath.evaluate(VARREF, assignment, XPathConstants.NODE);
		lvalue.setAttribute("field", leftValue);
		rvalue.setAttribute("name", rightValue);
		return (Element)doc.importNode(assignment, true);
	}
	
	/**
	 * Método encargado de devolver el Element donde se encuentra la delcaración de
	 * la variable pasada como parámetro.
	 * @param elemento que hace referencia al nombre de una variable. Puede tratarse de una declaración
	 * 			de variable o una referencia.
	 * @return Elemento tipo local-variable que representa la declaración de la variable
	 */
	public Element getDeclaration(Element var){
		if((Element)var.getElementsByTagName(LOCALVARIABLE).item(FIRST)!=null)
			// si la delcaración se encuentra en la linea de var (o sea, si se declara un local-variable en la misma linea)
			return var;
		else{
			Element father = (Element) var.getParentNode();
			return this.findDeclare((Element)father.getChildNodes().item(0), var, var);
		}
	}
	
	
	/**
	 * Método que busca la declaración de una variable local a un método de forma recursiva.
	 * @param brother
	 * 		  Nodo hermano de {@link actualLimit}
	 * @param actualLimit
	 * 		  Nodo hermano de {@link brother} que es el límite de consideración en la recursión.
	 * @param var
	 * 		  Variable de la que se desea encontrar la declaración.
	 * @return Element que representa la declaración de la variable <B>var</B>. Null si no se encontró
	 * 		   la declaración dentro del método donde se está buscando
	 */
	private Element findDeclare(Element brother,Element actualLimit,Element var) {
		if (brother.getTagName().equals(METHOD))//llegué a la declaración del método
			return null;
		if ( (brother !=null) && (this.isDeclaration(var,brother)) )
			return brother;
		else
			if(brother.compareDocumentPosition(var) == Element.DOCUMENT_POSITION_FOLLOWING)
				return this.findDeclare((Element) brother.getNextSibling(),actualLimit,var);
			else
				if (brother.compareDocumentPosition(actualLimit) == 0){
					Element father = (Element) brother.getParentNode();
					Element bigFather = (Element) father.getParentNode();
					return this.findDeclare((Element) bigFather.getChildNodes().item(FIRST),father,var);
				}									
		return null;
	}
	
	
	/**
	 * Método encargado de comprarar si el Element <B>item</B> es la declaración de la variable <B>var</B>
	 * @param var 
	 * 		  representa un nodo declaración, o bien, un nodo asignación del cual se extrae la variable
	 * 		  del lado izquierdo 		  
	 * @param item
	 * 		  Element del que se pretende saber si corresponde a la declaración del parámetro <B>var</B>.
	 * @return true si <B>item</B> es la declaración de <B>var</B>, false en caso contrario.
	 */
	private boolean isDeclaration(Element var, Element item) {
		String hasDeclaration = item.getTagName();
		if ( (hasDeclaration!=null) && (hasDeclaration.equals(LOCALVARIABLE)) )
			if (item.getAttribute("name").equals(this.getVarNameInAssignment(var)))
				return true;
		return false;
	}
	
	/**Sirve para extraer el nombre de la variable local a un método partiendo de su declaración o
	 * una sentencia de asignación.
	 * @param assignment
	 * 			elemento que representa una sentencia de declaración, una declaración con asignación
	 * 			incluida o simplemente una asignación.
	 * @return nombre de la variable declarada o asignanada*/	
	public String getVarNameInAssignment(Element assignment){
		String varName = assignment.getAttribute("name");		   
		//si assignment no tiene el nombre de la variable 
		//(xq resultó que se declaró más arriba de la línea donde se hace la asignación) 
		if (varName.compareTo("") == EQUAL){ 
			String varNameLocation = ASSIGLEFTVALUE+"/"+VARREF+"/@name";  
			try {
				varName = (String)xpath.evaluate(varNameLocation, assignment, XPathConstants.STRING);
			} catch (XPathExpressionException e) {
				System.out.println("Falló recuperación del nombre de la variable contenida en una asignación");
				e.printStackTrace();
			} 
		}
		return varName;		
	}
	
	
	/**Agrega una excepción a la lista de excepciones arrojadas por el método que se indican en su 
	 * declaración.
	 * @param method
	 * 			Element que representa el método al que se quiere anexar el lanzamiento de una excepción
	 * @param exception
	 * 			String que indica el nombre de la excepción lanzada que se anexará a la declaración del método
	 * */
	public void addExceptionThrown(Element method, String exception){
		try{
			if (!(Boolean)xpath.evaluate("self::"+THROWS+"[@exception='"+exception+"']", method, XPathConstants.BOOLEAN)){
				Element throwsSent = method.getOwnerDocument().createElement(THROWS);
				throwsSent.setAttribute("exception", exception);
				Element body = (Element)xpath.evaluate("self::"+BODYMETHOD, method, XPathConstants.NODE);
				method.insertBefore(throwsSent, body);
			}			
		}catch(XPathExpressionException e){
			e.printStackTrace();
		}
	}
	
	/**retorna el método al que pertenece una sentencia
	 * @param sentence
	 * 			sentencia de la cual se pretende conocer el método al que pertenece
	 * @return retorna el element del método al que pertenece la sentencia. Null si no pertenece a ningún método o si la sentencia misma es
	 * un elemento método.
	 **/
	public Element getOwnerMethod(Node sentence){
		try {
			return (Element)xpath.evaluate("ancestor::"+BODYMETHOD+"[1]", sentence, XPathConstants.NODE);
		} catch (XPathExpressionException e) {			
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**Permite obtener la expresion xpath que identifica unívocamente a un método java dentro de una clase,
	 * partiendo de su signatura. 
	 * @param method
     *             Element que representa la signatura de un método java              
     * @return     String que representa la expresión XPath del método indicado en el primer parámetro</BR>
     *             La expresión resultante respetará información acerca de:
     *                    -nombre del método
     *                    -tipo devuelto
     *                    -tipo y posición de cada parámetro formal
     *                Esta expresión puede ser empleada en una consulta XPath para hallar por ejemplo el cuerpo de dicho
     *                método en un documento. Una aplicación puede verse cuando se pretende conocer la implementación
     *                de un método por parte de una clase que extiende una interface que declara la signatura del método
     *                en cuestión.</BR>
     *             Ejemplo:
     *                 se tiene la siguiente declaración de método: public long suma(int a, int b);
     *                 la expresion xpath resultante será</BR>
     *                //method[@name="suma"][type[@name="long"] and formal-arguments[formal-argument[type[@name="int"]] and formal-argument[type[@name="int"]]] and block]"</BR>
     *                A partir de dicha expresion puedo traer desde un documento el nodo que la cumpla y así obtener todos los demás datos del método como por ejemplo el cuerpo
     *                del mismo.                
     */
   
    public String getXpathMethodExpression(String methodName, String returnType, ArrayList<String> paramsTypes){
        Element type = this.parseType(returnType);    	
    	String retorno = new String("//"+METHOD);
        retorno+= "[@name=\""+methodName+"\"]";
        String aux = this.typeToXpathBooleanQuery(type); 
        retorno+= aux.substring(FIRST, aux.lastIndexOf("]"));//le quito el ultimo cierre de corchete porque sino no me sirve para armar la expresion
        retorno+=" and "+ALLPARAMS;
        
        retorno+="[count("+PARAM+")='"+paramsTypes.size()+"']";
        if(paramsTypes.size() > 0){
            retorno+="[";           
            for(int i = 0; i < paramsTypes.size(); i++){
            	Element typeParam = this.parseType(paramsTypes.get(i));
                retorno+=PARAM+this.typeToXpathBooleanQuery(typeParam);               
                //si no llegué al último argumento
                if (i < paramsTypes.size()-1)
                    retorno+=" and ";       
            }
        }       
        retorno+="] and "+BODYMETHOD+"]";
        return retorno;
    }
    
    /**Dado un elemento type que puede ser simple o compuesto (parametrizado), devuelve la expresion xpath
     * booleana que sirve para hallar un nodo dentro de un documento, que respete exactamente la
     * estrucutura de dicho elemento type. <BR>
     * La lógica del método está basada en la estructura que java2xml crea para representar los tipos.<BR>
     * <PRE>
     * Ej: 
     * para el siguiente tipo: Hashtable&lt;String,Integer&gt; javaToXml genera la siguiente
     * estrucutra de árbol:
     * 	
     * 	&lt;type name="Hashtable"&gt;
	 *		&lt;type-argument&gt;
	 *			&lt;type name="String"/&gt;
	 *		&lt;/type-argument&gt;
	 *		&lt;type-argument&gt;
	 *			&lt;type name="Integer"&gt;
	 *		&lt;/type-argument&gt;
	 *	&lt;/type&gt;
	 *
	 *La expresión xpath resultante será:
     *[type[@name='Hashtable'][type-argument[type[@name='String']] and type-argument[type[@name='Integer']]]]
     *@param type
     *			elemento type del cual se pretende obtener la expresion xpath booleana que lo identifica
     *@return expresion xpath booleana que identifica al argumento type
     * </PRE>*/
    public String typeToXpathBooleanQuery(Element type) {
		String retorno="";
    	if (type.getTagName().equals(this.TYPE)){    		
			retorno+= "["+this.TYPE+this.elementAttToXpathBooleanQuery(type);
			NodeList innerTypes = type.getChildNodes();
			if (innerTypes.getLength()>0){//si se trata de un tipo parametrizado
				retorno+="[";			
				for (int i = 0; i < innerTypes.getLength();i++){
					retorno+=this.typeToXpathBooleanQuery((Element)innerTypes.item(i));
					if (i < innerTypes.getLength()-1)
						retorno+=" and ";
				}
				retorno+="]";
			}
			retorno+="]";
		}
    	else
    		if(type.getTagName().equals(this.PARAMETRIZED_TYPE))
    			retorno+=this.PARAMETRIZED_TYPE+this.typeToXpathBooleanQuery((Element)type.getFirstChild());    	
		return retorno;
	}
    
    /**Arma la porción de la expresion xpath booleana que sirve para consultar por la existencia de cada par &lt;atributo,valor&gt del elemento 
     * pasado como parámetro.<BR>
     * Ej: dado el elemento &lt;type dimensions="2" name="int" primitive="true"&gt la expresion xpath booleana resultante será: <BR>
     * [&#64;dimensions='2' and &#64;name='int' and &#64;primitive='true']
     * @param element
     * 			elemento que se utilzará para armar la expresion xpath que permite consultar por sus atributos
     * @return expresion xpath booleana que identifica a un tag. La misma no incluye sus nodos hijos sino sólo sus
     * atributos.*/
    private String elementAttToXpathBooleanQuery(Element element){
    	String retorno = "";
    	NamedNodeMap attributes = element.getAttributes();
    	if (attributes.getLength() > 0){
    		retorno+="[";
    		for (int i = 0; i<attributes.getLength(); i++){
    			retorno+="@"+attributes.item(i).getNodeName()+"='"+attributes.item(i).getNodeValue()+"'";
    			if (i<attributes.getLength()-1)
    				retorno+=" and ";
    		}
    		retorno+="]";
    	}
    	return retorno;
    }

    /**Dado un elemento que representa la signatura de un método y un documento que representa una 
     * clase, retorna el elemento que representa a la implementación del método dentro de la clase
     * @param methodSignature
     * 			signatura del método que será buscado dentro de la clase
     * @param sourceClass
     * 			clase dentro de la cual se buscará la implementación del método
     * @return elemento que representa la declaración y el cuerpo del método buscado, extraído de la 
     * clase sourceClass*/
	public Element getMethodImpl(Element methodSignature, Document sourceClass) {
    	String methodName;
		try {
			methodName = (String)xpath.evaluate("@name", methodSignature, XPathConstants.STRING);
		
    	String returnType = (String)xpath.evaluate(TYPE+"/@name", methodSignature, XPathConstants.STRING);
    	
        NodeList argumentsType = (NodeList)xpath.evaluate(ALLPARAMS+"/"+PARAM+"/"+TYPE+"/@name", methodSignature, XPathConstants.NODESET);
        ArrayList<String> paramsType = new ArrayList<String>();
        for(int i = 0; i < argumentsType.getLength(); i++){
            paramsType.add(paramsType.size(), argumentsType.item(i).getNodeValue());
        }
        String xpathMethodExpresion = this.getXpathMethodExpression(methodName, returnType, paramsType);
        
        return (Element)xpath.evaluate(xpathMethodExpresion, sourceClass, XPathConstants.NODE);

		} catch (XPathExpressionException e) {
			return null;
		}
    }
    
	 /**Dado una declaración de un método y un documento que representa una clase, retorna el elemento
	  * que representa a la implementación del método dentro de la clase
     * @param methodSignature
     * 			signatura del método que será buscado dentro de la clase
     * @param sourceClass
     * 			clase dentro de la cual se buscará la implementación del método
     * @return elemento que representa la declaración y el cuerpo del método buscado, extraído de la 
     * clase sourceClass*/
    public Element getMethodImpl(MethodDeclaration methodSignature, Document sourceClass) {
    	    	    	   	
    	String xpathMethodExp = this.getXpathMethodExpression(methodSignature.getMethodName(),methodSignature.getReturnType(), methodSignature.getParametersType()); 
    	
    	try {
			return (Element)xpath.evaluate(xpathMethodExp, sourceClass, XPathConstants.NODE);
		} catch (XPathExpressionException e) {
			return null;
		}
    	
    }
      
    
    /**
     * Método encargado de evaluar si el String pasado como parametro representa un tipo primitivo de dato. De ser asi devuelve true,
     * en caso contrario devuelve false.
     * @param type	tipo de dato a evaluar.
     * @return
     */
    public boolean isPrimitive(String type){
    	if (this.primitivesTypes.contains(type))
    		return true;
    	return false;
    }
    
    
    /**Convierte una clase fuente de java en un documento DOM
	 * @param src
	 * 			es la ruta del archivo del que se desea obtener el documento que lo representa
	 * @return org.w3c.dom.Document que representa al archivo fuente de java indicado en el parámetro*/
	public static org.w3c.dom.Document classToDOM(String src) {
		IXMLElement element = null;
		File file = new File(src);        
        try{
        	FileReader reader = new FileReader(src);
        	element = Java2XML.convertToXML(reader, file.getName());
        }catch (FileNotFoundException e){        	
        	e.printStackTrace();
        }catch(JXMLException e){
        	e.printStackTrace();
        }
        if (element == null){
        	System.out.println("Ocurrio un error levantando la clase " + src);
        	return null;
        }
        Document doc = null;
        try {
			doc = Domifier.toDom(element);
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
		return doc;
	}


	/**Agrega los nodos hijos del elemento source al final de la lista de nodos hijos del elemento dest.
	 * Si oldChilds es true borra todos los hijos de dest y luego agrega los de source. Si es false, mantiene los hijos
	 * de dest y agrega los de source al final de la lista*/
	public void cloneChilds(Element source, Element dest, boolean oldChilds) {
		
		if (oldChilds){
			NodeList c = dest.getChildNodes();
			for (int i = 0; i < c.getLength(); i++){
				dest.removeChild(c.item(i));
			}
		}
		
		NodeList childs = source.getChildNodes();		
		
		for (int i=0; i < childs.getLength(); i++){
			Node node = dest.getOwnerDocument().importNode(childs.item(i), true);
			dest.appendChild(node);
		}		
	}
	
	/**Dado un string que contiene un tipo de una variable, método, etc, retorna el elemento que
	 * la biblioteca javaToXml construye para representarlo (soporta tipos parametrizados).<BR>
	 * <PRE>
	 * Ejemplo: 
	 * dado el tipo: Hashtable&lt;String,Integer&gt; javaToXml retorna el siguiente elemento
	 * que lo identifica:
     * 	
     * 	&lt;type name="Hashtable"&gt;
	 *		&lt;type-argument&gt;
	 *			&lt;type name="String"/&gt;
	 *		&lt;/type-argument&gt;
	 *		&lt;type-argument&gt;
	 *			&lt;type name="Integer"&gt;
	 *		&lt;/type-argument&gt;
	 *	&lt;/type&gt;
	 *</PRE>
	 *@param typeDescriptor
	 *			tipo de cual se desea conocer la estructura con la que javaToXml lo representa
	 *@return {@link org.w3c.dom.Element} que representa al tipo pasado como parámetro según el 
	 *formato impuesto por la biblioteca javaToXml*/
	public Element parseType(String typeDescriptor){
		
		//a partir del string que contiene un tipo, le concateno lo necesario para que ese string represente el cuerpo de una clase java y de ese modo
		//poder utilizar la biblioteca java2xml para obtener la estructura xml que representa al tipo descripto por el parámetro typeDescriptor		
		String classDefinition = "public class E{"+typeDescriptor+" a;}";
		OutputStream w;
		try {
			w = new FileOutputStream("config"+System.getProperty("file.separator")+"tmpClass.java");
			w.write(classDefinition.getBytes());
			w.flush();
			w.close();
			Document doc = J2XMLManipulator.classToDOM("config"+System.getProperty("file.separator")+"tmpClass.java");
			Element retorno = (Element)xpath.evaluate("//"+this.TYPE+"[1]", doc,XPathConstants.NODE); 
			File file = new File("config"+System.getProperty("file.separator")+"tmpClass.java");
			file.delete();
			return retorno;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}				
	}

	/**Incluye en la declaracion de la clase representada por el parámetro source la 
	 * implementación de la interface cuyo nombre viene en el parámetro interfaceName.<BR>
	 * NOTA: el documento source debe corresponderse con la estructura de clase definida
	 * por la biblioteca javaToXml. Antes de realizar la modificación se realiza un chequeo
	 * para corroborar que la clase ya no implementa la interface. El chequeo es textual,
	 * es decir, si por ejemplo la clase antes de la modificación implementa "java.io.Serializable"
	 * y el parámetro interfaceName contiene la cadena "Serializable", entonces la modificación
	 * se llevará a cabo. Por otro lado, el método no tiene encuenta la importación
	 * de la interface a la lista de imports de la clase.
	 * @param interfaceName
	 * 			nombre de la interface que se desea agregar a la declaración.
	 * @param className
	 * 			indica el nombre de la clase que recibirá la modificación. Sirve para contemplar
	 * 			el caso de InnerClasses (clases definidas dentro de clases), puesto que en estos
	 * 			casos existirá un solo documento para englobar a todas ellas.
	 * @param source
	 * 			documento que respeta la estrucutura impuesta por la biblioteca javaToXml
	 * 			que representa la clase sobre la cual se realizará la modificación.
	 * @see implementación de "alreadyImplementInterface(String interfaceName, String className, Document source)"*/
	public void setImplementInterface(String interfaceName, String className, Document source) {
		try {
			if (!alreadyImplementInterface(interfaceName,className,source)){				
					Element interfaceElement = this.createNewTag(source, this.IMPLEMENT, "");
					interfaceElement.setAttribute("interface", interfaceName);
					//expresion xpath: //class[@name='Sentence']/node()[name()!='extends-list'][1]
					Element nextBrother = (Element)xpath.evaluate("//"+this.CLASS+"[@name='"+className+"']/node()[name()!='"+this.EXTENDS+"'][1]", source,XPathConstants.NODE);
					nextBrother.getParentNode().insertBefore(interfaceElement, nextBrother);			
				}
			
		} catch (XPathExpressionException e) {
				e.printStackTrace();
		}		
	}
	
	
	/**@param interfaceName
	 * 			nombre de la interface a buscar
	 * @param className
	 * 			nombre de la clase sobre la que se buscará si contiene la palabra reservada "implements"
	 * 			con el nombre de la interface pasado como primer parámetro
	 * @param source
	 * 			clase sobre la que se chekeará si ya implementa la interface pasada como parámetro
	 * @return true si la clase implementa la interface pasada como primer parámetro, false en caso 
	 * contrario.<BR>
	 * NOTA: la comparación es textual. Significa que por ejemplo comparar "java.io.Serializable"
	 * vs "Serializable" arrojará falso
	 * @throws XPathExpressionException */
	public boolean alreadyImplementInterface(String interfaceName, String className, Document source) throws XPathExpressionException {
		
		return (Boolean)xpath.evaluate("//"+this.CLASS+"[@name='"+className+"']/"+this.IMPLEMENT+"[@interface='"+interfaceName+"']", source,XPathConstants.BOOLEAN);
			 
	}

	/**Agrega un nuevo atributo sobre el primer atributo de la clase destClass
	 * @param field
	 * 			elemento que representa el atributo a ser agregado
	 * @param destClass
	 * 			documento que representa la clase sobre la cual se inserta el nuevo atributo
	 * <BR>NOTA: se asume que el elemento field ya viene importado en el documento destClass.
	 **/
	public void addAttribute(Element field, Document destClass) {
		try {
			Element firstField = (Element)xpath.evaluate("//"+this.CLASS+"/"+this.ATTRIBUTE, destClass,XPathConstants.NODE);
			if (firstField!= null)
				firstField.getParentNode().insertBefore(field, firstField);
			else{
				Element firstMethod = (Element)xpath.evaluate("//"+this.CLASS+"/"+this.METHOD, destClass,XPathConstants.NODE);
				firstMethod.getParentNode().insertBefore(field, firstMethod);								
			}
		} catch (XPathExpressionException e) {
			System.out.println("Error al intentar anexar un nuevo atributo a la clase");
			e.printStackTrace();
		}
		
	}
	
	/**Agrega un nuevo método debajo del último método de la clase destClass
	 * @param newMethod
	 * 			elemento que representa el método a ser agregado
	 * @param destClass
	 * 			documento que representa la clase que recibirá la inserción del nuevo método
	 * */
	public void addMethod(Element newMethod, Document destClass){
		
		Node classTag = destClass.getElementsByTagName(this.CLASS).item(0);
		classTag.appendChild(newMethod);
		
	}

	/**Dado un documento que representa una clase retorna el nombre completo de la misma incluido los paquetes dentro la de cual se halla contedida
	 * @param classDocument
	 * 			documento que representa una clase java
	 * @return un string con el nombre completo de la clase. La separación de paquetes se indica con el caracter "."*/
	public static String getClassName(Document classDocument) {
		
		String className = ((Element)classDocument.getElementsByTagName("class").item(FIRST)).getAttribute("name");		
		
		NodeList packageElements = classDocument.getElementsByTagName("package-decl");
		if (packageElements.getLength()== 0){
			return className;
		}
		String packageName = ((Element)packageElements.item(FIRST)).getAttribute("name");
		return packageName+"."+className;		
	}

}
